﻿#region "Copyright (C) Lenny Granovsky. 2005-2013"
//This program is free software distribute under the terms of the GNU General Public License as published by
//the Free Software Foundation. Please read AssemblyInfo.cs file for more information.
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Reflection;
using System.IO;

namespace Web.Enhancements.Rest
{
    public class CallingTargetFactory
    {
        private static object lockObject;

        static CallingTargetFactory()
        {
            lockObject = "";
        }

        protected object CreateInvocationContext(string appVirtualPath, string queryString, string httpMethod)
        {
            WebRestServiceContext.CreateCurrentInstance();

            string cacheKey = "";
            object target = this.FindCallingTarget(appVirtualPath, queryString, httpMethod, out cacheKey);
            InfoCaching.MethodInfoCacheItem info = InfoCaching.MethodInfoCache.Current[cacheKey];
            System.Web.Services.Protocols.LogicalMethodInfo methodInfo = info.Method;

            //create context info into for Handler to use.
            WebRestServiceContext.Current.MethodInfo = methodInfo;
            WebRestServiceContext.Current.MethodInfoCache = info;
            WebRestServiceContext.Current.ContentType = info.ContentType;
            WebRestServiceContext.Current.SessionMode = info.SessionMode;
            WebRestServiceContext.Current.ContentDisposition = info.ContentDisposition;
            WebRestServiceContext.Current.JsonSerializer = info.JsonSerializer;

            //build list of REST Filters
            this.CreateContextFiltersCollection();

            return target;
        }

        /// <summary>
        /// Method extracts collection of filters from the method interface, and adds filters to current WebRestServiceContext.
        /// </summary>
        protected void CreateContextFiltersCollection()
        {
            //build list of REST Filters
            foreach (Filters.IWebRestFilterAttribute attribute in WebRestServiceContext.Current.MethodInfoCache.FilterAttributes)
                WebRestServiceContext.Current.Filters.Add(attribute.CreateFilter());
        }

        /// <summary>
        /// Method finds calling target and returnes target object and method's cache key.
        /// </summary>
        /// <param name="context"></param>
        /// <returns>Returnes target object.</returns>
        protected object FindCallingTarget(string appRelativeCurrentExecutionFilePath, string queryString, string httpMethod, out string cacheKey)
        {
            //map request to Type
            TypeMapper mapper = new TypeMapper();
            MappingResult mapping = mapper.MapRequest(appRelativeCurrentExecutionFilePath, queryString, httpMethod);

            //build invocation target object
            object target = BuildInvocationTarget(mapping.ClassTypeName, mapping.AssemblyName);

            //build method info
            System.Web.Services.Protocols.LogicalMethodInfo methodInfo = null;
            string key = InfoCaching.MethodInfoCache.CreateKey(mapping.ClassTypeName, mapping.MethodName);
            InfoCaching.MethodInfoCacheItem cachedMethod = null;

            if (!InfoCaching.MethodInfoCache.Current.ContainsKey(key))
            {
                MethodInfo info = null;
                MethodInfo[] mis = target.GetType().GetMethods(BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
                object[] tmp = null;
                foreach (MethodInfo mi in mis)
                {
                    //if this is not default mapping, then chek for specific method by its name
                    if (!mapping.IsDefault)
                    {
                        //handle multiple methods in case there is an overloading and only single method is WebRestMethod
                        if (string.Compare(mi.Name, mapping.MethodName, true) == 0)
                        {
                            tmp = mi.GetCustomAttributes(typeof(WebRestMethodAttribute), false);
                            if (tmp.Length > 0)
                            {
                                info = mi;
                                break;
                            }
                        }
                    }
                    else //if this is default mapping, then locate the method mapped to specific HTTP Method by default.
                    {
                        tmp = mi.GetCustomAttributes(typeof(WebRestMethodAttribute), false);
                        if (tmp.Length > 0)
                        {
                            tmp = mi.GetCustomAttributes(typeof(DefaultHttpMethodAttribute), false);
                            if (tmp.Length > 0)
                            {
                                if (string.Compare((tmp[0] as DefaultHttpMethodAttribute).HttpMethod, mapping.MethodName, true) == 0)
                                {
                                    info = mi;
                                    break;
                                }
                            }
                        }
                    }
                }
                if (info == null)
                    throw new HttpException(System.Net.HttpStatusCode.NotFound.GetHashCode(), string.Format(ErrorMessages.ResourceManager.GetString("InvalidMethodNameRequested"), mapping.MethodName));

                methodInfo = new System.Web.Services.Protocols.LogicalMethodInfo(info);
                cachedMethod = new InfoCaching.MethodInfoCacheItem(methodInfo);
                if (!InfoCaching.MethodInfoCache.Current.ContainsKey(key)) //extra check
                {   //handle multithreading before adding
                    lock (lockObject)
                    {
                        if (!InfoCaching.MethodInfoCache.Current.ContainsKey(key)) //extra check
                            InfoCaching.MethodInfoCache.Current.Add(key, cachedMethod);
                    }
                }
            }
#if TRACE
            System.Diagnostics.Trace.WriteLine(string.Format("Invocation Target: {0}; Method: {1}.", target.GetType().AssemblyQualifiedName, mapping.MethodName));
#endif
            cacheKey = key;
            return target;
        }

        protected object BuildInvocationTarget(string className, string assemblyName)
        {
            try
            {
                object target = null;
                if (assemblyName.Length == 0)
                {
                    //try local assembly - this is just for WebRestExplorer
                    Type type = Type.GetType(className, false, true);
                    if (target == null)
                    {
                        //now try for each assembly in the app domain.
                        type = TryAllDomainAssemblies(className);
                        //if nothing found, try to assure all required assemblies are loaded and then try again
                        if (type == null)
                        {
                            foreach (string asmName in WebRestSettings.Current.AssureAssemblies)
                                AppDomain.CurrentDomain.Load(asmName);
                            type = TryAllDomainAssemblies(className);
                        }
                        //throw an exception if nothing helps
                        if (type == null)
                            throw new HttpException(System.Net.HttpStatusCode.NotFound.GetHashCode(), ErrorMessages.ResourceManager.GetString("FailedToResolveTypeAssemblyNotFound"));
                    }
                    target = Activator.CreateInstance(type);
                    TypeMapper.InsertDeterminedAliasMapping(type.FullName, type.Assembly.FullName);
                }
                else
                {
                    target = Activator.CreateInstance(assemblyName, className);
                    target = ((System.Runtime.Remoting.ObjectHandle)target).Unwrap();
                }
                return target;
            }
            catch (Exception e)
            {
                throw new WebRestException(ErrorMessages.ResourceManager.GetString("HandlerCannotCreateTypeInstance"), className, e);
            }
        }

        protected Type TryAllDomainAssemblies(string className)
        {
            Type type = null;
            Assembly[] assemlies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly asm in assemlies)
            {
                type = asm.GetType(className, false, true);
                if (type != null)
                    break;
            }
            return type;
        }
    }
}
